/*
>> StackOp.s
>>
>> Stack-based operation, allowing buffer and scalar inputs, and
>> buffer outputs. Adapted from NeighbourhoodToPixelOp.s
>>
>> Author: Wouter Caarls
>> Date:   23-06-2005
*/

#include "kernelsplit.h"

// For kernel:
//   Must have equal number of inputs and output streams
//   Uses enqueue operation to add stuff to stack

// For skeleton:
//   First argument is stack (binary image for stack initialization)
//   Other arguments are stream input and output, and scalar input

typedef struct stack_item_s
{
  short int x, y;
} stack_item_t;

stratego code makeabsolute(code param, int type, code body, int stride)
{
  strategies
    main = ?(param, type, body, stride); !(body, (param, type, stride));
      try(env-alltd(MakeAbsolute)); bottomup(try(apply-conc))

  rules
    MakeAbsolute:
      (ArrayIndex(ArrayIndex(p, i1), i2), (p, t, s)) ->
      c:exp |[ ~exp:p [ (_rstack[_ii].y + ~exp:i1) * ~exp:s + _rstack[_ii].x + ~exp:i2 ] ]|
      where (<id>(t) => IntConst("0"))

    MakeAbsolute:
      (ArrayIndex(ArrayIndex(p, i1), i2), (p, t, s)) ->
      c:exp |[ ~exp:p [ (_rstack[_ii].y + ~exp:i1) * ~exp:s + _rstack[_ii].x + ~exp:i2 ] ]|
      where (<id>(t) => IntConst("1"))

    MakeAbsolute:
      (p, (p, t, s)) -> c:exp |[ &~exp:p [ _rstack[_ii].y * ~exp:s + _rstack[_ii].x ] ]|
      where (<id>(t) => IntConst("1"))

    MakeAbsolute:
      (p, (p, t, s)) -> p
      where (not(<id>(t) => IntConst("0") + <id>(t) => IntConst("1")))

    apply-conc:
      Conc(list1, list2) -> <conc>(list1, list2)
}

void addtostack(int _y, int _x)
{
  if (!_markim[(_rstack[_ii].y + _y) * _stride + _rstack[_ii].x + _x] && 
      (_rstack[_ii].y + _y > 0 && _rstack[_ii].y + _y < _lines-1) &&
      (_rstack[_ii].x + _x > 0 && _rstack[_ii].x + _x < _stride-1))
  {
    _markim[(_rstack[_ii].y + _y) * _stride + _rstack[_ii].x + _x] = 1;
    _wstack[_wsp].y = _rstack[_ii].y + _y;
    _wstack[_wsp].x = _rstack[_ii].x + _x;
    _wsp++;
  }
}

strategy StackOp(code args, code arguments, code body)
{
	
int _ii, _jj, _kk, _stride, _lines, _line, _fi = -1, _calls=0;
stack_item_t *_rstack, *_wstack, *_tstack;
int _rsp, _wsp, _tsp, _iterations=0;
void *_temp;
code splitbody[];
unsigned char *_markim;

/* Get stream strides */
/* NOTE: ASSUMES ALL STRIDES ARE EQUAL */
_stride = scuBufferGetStride(args[0].buffer);
_lines = scuBufferGetLines(args[0].buffer);

assert((_rstack = (stack_item_t*) malloc(_stride*_lines*sizeof(stack_item_t))));
assert((_wstack = (stack_item_t*) malloc(_stride*_lines*sizeof(stack_item_t))));
assert((_markim = (unsigned char*) malloc(_stride*_lines*sizeof(unsigned char))));
memset(_markim, 0, _stride*_lines*sizeof(unsigned char));

@declare(addpointer(args[0].datatype), args[0].id);

for (_ii=1; _ii < arguments; _ii++)
{
  @declare(addpointer(args[_ii].datatype), args[_ii].id);

  if (args[_ii].argtype == ARG_STREAM_IN || args[_ii].argtype == ARG_STREAM_OUT)
  {
    assert((@args[_ii].id = @cast(addpointer(args[_ii].datatype),
			    malloc(_stride*_lines*sizeof(args[_ii].datatype)))));
  }
  else
  {
    /* Non-streams must be initialised */
    if (@dataclass(args[_ii].datatype) == DATACLASS_INTEGER)
      @args[_ii].id = @cast(addpointer(args[_ii].datatype), `&args[_ii].data.d`);
    else if (@dataclass(args[_ii].datatype) == DATACLASS_FLOAT)
      @args[_ii].id = @cast(addpointer(args[_ii].datatype), `&args[_ii].data.f`);
    else
      @args[_ii].id = @cast(addpointer(args[_ii].datatype), `args[_ii].data.b`);
  }

  /* Rewrite body */
  body = makeabsolute(args[_ii].id, args[_ii].argtype, body, _stride);
}

splitbody = @kernelsplit(body);

// Load stack and inputs
_line = 0;
_rsp = 0;
while (scuBufferPeek(args[0].buffer, args[0].reader,
		     (void**)&(@args[0].id), _stride, SCU_BUF_BLOCK_ALL) == _stride)
{
  _jj=0;
  for (_ii=1; _ii < arguments; _ii++)
    if (args[_ii].argtype == ARG_STREAM_IN)
    {
      scuBufferRead(args[_ii].buffer, args[_ii].reader,
                    (void*)&((@args[_ii].id)[_line*_stride]), _stride*sizeof(args[_ii].datatype), SCU_BUF_BLOCK_ALL);
      for (_jj++; _jj < arguments && args[_jj].argtype != ARG_STREAM_OUT; _jj++);
      memcpy(&((@args[_jj].id)[_line*_stride]), &((@args[_ii].id)[_line*_stride]), _stride*sizeof(args[_ii].datatype));
    }

  for (_ii=-0; _ii < _stride; _ii++)
  {
    if ((@args[0].id)[_ii] > *@args[1].id)
    {
      _rstack[_rsp].x = _ii;
      _rstack[_rsp].y = _line;
      _rsp++;
    }
  }
  _line++;

  scuBufferReleasePeeked(args[0].buffer, args[0].reader, _stride);
}

// Remove input references
for (_ii=0; _ii < arguments; _ii++)
  if (args[_ii].argtype == ARG_STREAM_IN)
     scuBufferRemoveReference(args[_ii].buffer);

@splitbody[0];

// Do stuff
while (_rsp && _iterations < *@args[2].id)
{
  _wsp = 0;

  lprintf(STREAM(ilpl.crawl), "Processing %d pixels", _rsp);

  /* Run operation */	
  for (_ii=0; _ii < _rsp; _ii++)
  {
    @splitbody[1];
  }

  /* Swap input and output */
  _jj = 0;
  for (_ii=1; _ii < arguments; _ii++)
  {
    if (args[_ii].argtype == ARG_STREAM_IN)
    {
      for (_jj++; _jj < arguments && args[_jj].argtype != ARG_STREAM_OUT; _jj++);
      
      _temp = @args[_ii].id;
      @args[_ii].id = @args[_jj].id;
      @args[_jj].id = _temp;
    }
  }

#if 0
  /* Perhaps make decision to copy based on number of pixels in stack */
  /* Make changes in new output as well */
  for (_ii=0; _ii < _rsp; _ii++)
  {
    _jj=0;
    for (_kk=1; _kk < arguments; _kk++)
      if (args[_kk].argtype == ARG_STREAM_IN)
      {
	for (_jj++; _jj < arguments && args[_jj].argtype != ARG_STREAM_OUT; _jj++);

	(@args[_jj].id)[(_rstack[_ii].y * _stride + _rstack[_ii].x)] =
	  (@args[_kk].id)[(_rstack[_ii].y * _stride + _rstack[_ii].x)];
        _markim[_rstack[_ii].y * _stride + _rstack[_ii].x] = 0;
      }
  }
#else
  _jj=0;
  for (_kk=1; _kk < arguments; _kk++)
    if (args[_kk].argtype == ARG_STREAM_IN)
    {
      for (_jj++; _jj < arguments && args[_jj].argtype != ARG_STREAM_OUT; _jj++);

      memcpy((@args[_jj].id), (@args[_kk].id), _lines*_stride*sizeof(args[_jj].datatype));
      memset(_markim, 0, _stride*_lines*sizeof(unsigned char));
    }
#endif

  /* Swap stacks */
  _tstack = _rstack;
  _rstack = _wstack;
  _wstack = _tstack;

  _calls = _calls + _rsp;
  _rsp = _wsp;
  _iterations++;
}

@splitbody[2];

lprintf(STREAM(ilpl.info), "%d kernel calls", _calls);

/* Swap input and output */
_jj = 0;
for (_ii=1; _ii < arguments; _ii++)
{
  if (args[_ii].argtype == ARG_STREAM_IN)
  {
    for (_jj++; _jj < arguments && args[_jj].argtype != ARG_STREAM_OUT; _jj++);
    
    _temp = @args[_ii].id;
    @args[_ii].id = @args[_jj].id;
    @args[_jj].id = _temp;
  }
}

// Write outputs
for (_line=0; _line < _lines; _line++)
{
  for (_ii=1; _ii < arguments; _ii++)
    if (args[_ii].argtype == ARG_STREAM_OUT)
      scuBufferWrite(args[_ii].buffer, (void*)&(@args[_ii].id)[_line*_stride],
		     _stride*sizeof(args[_ii].datatype), SCU_BUF_BLOCK_ALL);
}

// Finalize outputs
for (_ii=1; _ii < arguments; _ii++)
  if (args[_ii].argtype == ARG_STREAM_OUT)
  scuBufferFinalize(args[_ii].buffer);

// Free stuff
free(_rstack);
free(_wstack);
for (_ii=1; _ii < arguments; _ii++)
  if (args[_ii].argtype == ARG_STREAM_IN || args[_ii].argtype == ARG_STREAM_OUT)
      free(@args[_ii].id);

}
